#include "ClientConsole.h"
#include "../core/Logger.h"

#include <iostream>
#include <string>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "SDL.h"
#include "SDL_net.h"

const int ClientConsole::BUFFER_SIZE = 1024;
const std::string ClientConsole::END_LINE = "\r\n";

ClientConsole::ClientConsole(NetConsole *parent, TCPsocket sock, std::string name)
{
	_parent = parent;
	_sock = sock;
	_name = name;
	_isRunning = true;
	
	_sendLock = SDL_CreateMutex();
	_recvLock = SDL_CreateMutex();
	
	// Start socket thread
	_clientThread = SDL_CreateThread(clientMain, this);
	if ( _clientThread == NULL ) 
	{
		LOG(logERROR) << "Unable to create client thread (" << _name << "): " << SDL_GetError();
		return;
	}	
}

ClientConsole::~ClientConsole() 
{
	// Flag that the client should shut down
	_isRunning = false;
	
	LOG(logINFO) << "Waiting for thread to die...";
	// Wait for listening thread
	SDLNet_TCP_Close(_sock);
	SDL_WaitThread(_clientThread, NULL);
	LOG(logINFO) << "Done. Destroying mutexes...";
	
	// Destroy mutexes
	SDL_DestroyMutex(_sendLock);
	SDL_DestroyMutex(_recvLock);
	LOG(logINFO) << "Done.";
}

int ClientConsole::clientMain(void *owner)
{
	ClientConsole* client = (ClientConsole *)owner;
	char *buffer = new char[BUFFER_SIZE+1];
	std::stringstream outBuffer;
	std::string tBuffer;
	
	// Handle IO from the socket
    while ( client->_isRunning ) 
	{
		// Receive new bytes from socket
		int result = SDLNet_TCP_Recv(client->_sock, buffer, BUFFER_SIZE);
		if (result < 0)
		{
			LOG(logERROR) << "SDLNet_TCP_Recv (" << client->_name << "): " << SDLNet_GetError();
			client->_parent->removeClient(client);
			return -1;
		}
		else if (result == 0)
		{
			// Connection was closed
			LOG(logINFO) << "Client disconnected: " << client->_name;
			client->_parent->removeClient(client);
			return 0;
		}
		
		// Insert bytes into stringstream buffer
		buffer[result] = '\0';
		outBuffer << buffer;
		tBuffer += std::string(buffer);
		
		// Search for completed lines
		std::string outMessage;
		while( client->_isRunning ) {
			
			// Pull complete line out of stringstream buffer
			//getline(outBuffer, outMessage);
			std::string outMessage = outBuffer.str();
			int findpos = tBuffer.find('\n');
			if(findpos != std::string::npos)
			{
				outMessage = tBuffer.substr(0, findpos);
				tBuffer = tBuffer.substr(findpos+1, tBuffer.size());
			//if (outBuffer.fail()) 
			//{
			//	outBuffer.clear();
			//	break;
			//} 
				// If this isn't a hardcoded command, put it in queue
				if (!client->handleMessage(outMessage))
				{
					SDL_mutexP(client->_recvLock);
					client->_outMessages.push_back(outMessage);
					SDL_mutexV(client->_recvLock);
				}
			}
			else
			{
				break;
			}
		}
    }
    
	// Close the client socket
	SDLNet_TCP_Close(client->_sock);
	return 0;
}

bool ClientConsole::handleMessage(std::string& message)
{
	// Check if this was a connect command
	int match = message.compare(0, 8, "connect(");
	if ( match == 0 )
	{
		// If it was, check that valid parameters were provided
		size_t commaPos = message.find_first_of(",");
		size_t rparenPos = message.find_first_of(")");
	
		if ( commaPos != std::string::npos && rparenPos != std::string::npos )
		{
			// Parse the sheet ID and channel ID from the arguments
			int sheetId = strtol(message.substr(8, commaPos-8).c_str(), NULL, 10);
			int chanId = strtol(message.substr(commaPos+1, rparenPos-(commaPos+1)).c_str(), NULL, 10);
			
			// Clear any existing, queued up messages
			SDL_mutexP(_recvLock);
			_outMessages.clear();
			SDL_mutexV(_recvLock);
			
			// Switch to the new channel
			_parent->_core->removeConsole(this);
			int actualChanId = _parent->_core->addConsole(this, sheetId, chanId);
		
			std::stringstream connectMsg;
			connectMsg << "Switched console to (" << sheetId << ", " << actualChanId << ").";
			sendMessage(connectMsg.str());
			LOG(logINFO) << connectMsg.str();
			return true;
		}
	}
	
	return false;
}

void ClientConsole::sendMessage(const std::string& message)
{	
	// Add newline to end of message
	std::string fullMsg = message + END_LINE;
	
	// Send out message over socket
	SDL_mutexP(_sendLock);
	int result = SDLNet_TCP_Send(_sock, fullMsg.c_str(), fullMsg.size());
	SDL_mutexV(_sendLock);
	
	// Upon socket failure, disconnect
	if((unsigned int)result < message.size()) 
	{
		LOG(logERROR) << "SDLNet_TCP_Send (" << _name << "): " << SDLNet_GetError();
		_parent->removeClient(this);
	}
}

std::vector<std::string> ClientConsole::receiveMessages()
{
	SDL_mutexP(_recvLock);
	std::vector<std::string> msgs = _outMessages;
	_outMessages.clear();
	SDL_mutexV(_recvLock);
	
	return msgs;
}